import { Function } from '../Function'
import { ObjectTool } from './ObjectTool'
import { TreeTool } from './TreeTool'

/**
 * 数据工具
 * @author 冰凝
 * @date 2022年9月19日 09点50分
 **/
export class ArrayTool {

    public static treePath = TreeTool.treePath
    public static treeEach = TreeTool.treeEach
    public static buildTree = TreeTool.buildTree

    /**
     * 数组是空哒?
     * @param {any[]} arr 待检查数组
     * @return {boolean} ?
     */
    public static isEmpty(arr?: any[]): boolean {
        if (ObjectTool.isNull(arr)) {
            return true
        }
        if (!Array.isArray(arr)) {
            return true
        }
        return arr.length === 0
    }

    /**
     * 数组非空?
     * @param {any[]} arr 待检查数组
     * @return {boolean} ?
     */
    public static isNotEmpty(arr?: any[]): boolean {
        return !this.isEmpty(arr)
    }

    // noinspection JSValidateJSDoc
    /**
     * 分组
     * @param {S[]} arr 源数据
     * @param {(i: S) => string} keyFun Key 映射
     * @return {Record<string, S[]>}
     */
    public static groupBy<S = any>(arr: S[], keyFun: (i: S) => string): Record<string, S[]> {
        if (this.isEmpty(arr)) {
            return {}
        }
        ObjectTool.requireNotNull(keyFun)
        return arr?.reduce((pv, cv) => {
            const key = keyFun(cv)
            // @ts-ignore
            if (this.isEmpty(pv[key])) {
                // @ts-ignore
                pv[key] = [ cv ]
            } else {
                // @ts-ignore
                pv[key].push(cv)
            }
            return pv
        }, {})
    }

    /**
     * 去重
     * @param sourceArr 源数组
     * @param notNull 是否保留空值, 默认不保留(true)
     */
    public static distinct<E extends string | number | boolean | null | undefined>(sourceArr?: Array<E>, notNull: boolean = true): Array<E> {
        if (ArrayTool.isEmpty(sourceArr)) {
            return []
        }
        return Array.from(new Set(notNull ? sourceArr?.filter(i => ObjectTool.isNotNull(i)) : sourceArr))
    }

    /**
     * 数组 转 Map (对象)
     * 如果 keyFun 返回值(最终key)相同, 按照源数组顺序覆盖
     * @para arr 源数组
     * @param arr
     * @param keyFun key 映射
     * @param valueFun value 映射 可选, 默认 为: S => S
     * @return 结果
     */
    public static toMap<S, R>(arr: S[], keyFun: Function<S, string>, valueFun?: Function<S, R>): Record<string, R> {
        if (this.isEmpty(arr)) {
            return {}
        }
        ObjectTool.requireNotNull(keyFun)

        return arr.reduce((pv, cv) => {
            // @ts-ignore
            pv[keyFun(cv)] = ObjectTool.requireNotNullDefault(valueFun, ( (i: S) => i ) as any)(cv)
            return pv
        }, {})
    }

    /**
     * 嵌套数组展开
     * <p>
     * 例:
     * <pre>
     *     const arr = [
     *     {items: [1, 2, 3]},
     *     {items: [4, 5, 6]},
     *     ]
     *
     *     // res: [1, 2, 3, 4, 5, 6]
     *     const res = CollUtil.flatMap(arr, i => i.items)
     * </pre>
     * @param arr 源数组
     * @param map 源数组映射, 结果为数组类型
     * @return map 数组合并后的结构
     * @deprecated 使用 {@link Array.flatMap()}替代
     */
    public static flatMap<S, R>(arr: S[], map: Function<S, R[]>): R[] {
        if (this.isEmpty(arr)) {
            return []
        }
        ObjectTool.requireNotNull(map)
        return arr.map(map)
            .filter(i => this.isNotEmpty(i))
            .reduce((pv, cv) => ( [ ...pv, ...cv ] ), [])
    }
}
